#ifndef __PORT_H__
#define __PORT_H__

#include <list>
#include <set>
#include "common.h"
#include "singletonmodel.h"
#include "timer.h"
#include "socketaddress.h"
#include "ntdproto.h"
#include "message.h"

/*
HowTo

The Port is used to abstract the TCP and UDP transport. You can create UDPPort and TCPPort, 
the TCPPort will Open a socket for listening, and UDPPort will Open a socket for receiving.
When there's incoming traffic on the socket, a connection will be created automatically,
and will notify through IConnListener::OnCreated.

You can write and read the connection when there're corresponding events on the connection.
When there's an error, the connection can be deleted.

There'll by default probing packets sent on the connection, and the metrics about the network 
will be connected and put into IConnMonitor. The Probe can be disabled by passing INT_MAX to 
Connection::SetProbingInterval().

TODO:
1. How to avoid clock skew problem without causing too much overhead to the network?

2. Will the intermediate node treat different size of packets differently?

3. The probing rate can be adjusted according to the APP's sending rate, and will 
automatically use the APP data as probing packets by adding specific headers to the PDU.

4. Need some encapsulate of th evbuffer.

5. Should the connection provides buffer for the APPs? I'd rather keep the connection simple 
   and design another class to handle the buffer relating issue. Currently, only the probing
   relating packets are buffered inner connection.

6. Split packets is also not responsibility of the connection.

7. Need thread post event support

8. Call API in construct is ulgy, should avoid
*/



enum Option {
	OPT_DONTFRAGMENT,
	OPT_REUSEADDR,
	OPT_RCVBUF,
	OPT_NODELAY,
};
int SetOption(evutil_socket_t s, Option opt, int value);

/*
We need this interface for the following reasons.

1. Ratelimit connection is needed.
2. Buffered connection is needed, currently, the Connection won't guarrantee the data succeed when sending, 
   so data should be buffered.
3. PseudoTcp connection is needed. I'm think about using this kind of connection to simulate TCP via UDP, thus
   to group udp connection and tcp conection togather, however, maybe there's no need to do so if there're simple
   methods.

All the above kind of connection can be implemented by subclassing the IConnection interface and wrap the real TCP
or UDP connection.
*/

class IConnection
{
public:
	virtual ~IConnection() {}
	virtual int Send(byte* data, size_t size) = 0;

	virtual uint16 GetConvId() = 0;

	//Set interval of probing, if set to INT_MAX, will disable active probing, but still can process probing packets
	virtual void SetProbingInterval(int ms) = 0;
	virtual bool IsTimetoPing(uint32 now) = 0;

	//The ping can be sent using APP data or Ping packets
	//The default connection use Ping packets, but if the buffered connection, it can use ADD data, simply sent data
	//out, and the underlying connection will decide automatically, if failed, should cope with the failure, and try
	//ping when OnWrite Callbacked.
	virtual void DoPing() = 0;

	virtual bool Connected() const = 0;

	//if disabled, the connection will not process probing packets and will forward all data read to the app layer
	virtual void EnablePing(bool enabled) = 0;

	enum PROTO_TYPE{
		UNKNOWN,
		PROTO_TCP,
		PROTO_UDP,
		PROTO_PSEUDOTCP,
	};
	virtual PROTO_TYPE GetProtoType() = 0;

	virtual const SocketAddress& RemoteAddress() = 0;
};

class IConnListener
{
public:
	virtual void OnCreated(IConnection* pConn) = 0;
	virtual void OnConnected(IConnection* pConn) = 0;
	//should keep a copy of the data
	virtual void OnReadPacket(IConnection* pConn, const char* data, size_t size) = 0;
	virtual void OnWritable(IConnection* pConn, bool writable) = 0;
	virtual void OnClose(IConnection* pConn, int err) = 0;
	virtual void OnError(IConnection* pConn, int err) = 0;
	virtual void OnDestroy(IConnection* pConn) = 0; 
};

class DisposeConnListener : public IConnListener
{
public:
	virtual void OnCreated(IConnection* pConn) {}
	virtual void OnConnected(IConnection* pConn) {}
	//should keep a copy of the data
	virtual void OnReadPacket(IConnection* pConn, const char* data, size_t size) {}
	virtual void OnWritable(IConnection* pConn, bool writable) {}
	virtual void OnClose(IConnection* pConn, int err) {}
	virtual void OnError(IConnection* pConn, int err) { Dispose(pConn); }
	virtual void OnDestroy(IConnection* pConn) {}
};

/*
Probing Manager takes control of how to send probing packets of all connections.
Later this can be abstract as an Interface.
Now this class only make all connections registered ping at the same tick, each Connection has its own tick,
and the precision can be set for the tick.
*/
class ProbingManager: public SingletonModel<ProbingManager>, public INTDTimerListener
{
public:
	ProbingManager();
	virtual ~ProbingManager();
	void AddConnection(IConnection * pConn);
	void RemoveConnection(IConnection * pConn);

	void SetPrecision(int ms);//precision in ms,default 10ms
	void SetProbingInterval(int ms);
	void SetProbingInterval(IConnection * pConn, int ms);

	//This will be called whenever the connection itself trigger the ping
	//Other Connections will check if it need to ping now
	void OnTick();
	
	virtual void OnTimer(NTDTimer* pTimer);

protected:
	void PingIfNeeded(uint32 now, IConnection * pConn);

private:
	NTDTimer probe_timer_;
	int precision_tick_;
	int interval_;
	std::set<IConnection*> conn_set_;
	bool notifying_;
};


class Connection;

class Port
{
public:
	Port() { socket_ = INVALID_SOCKET; error_ = 0;}
	virtual ~Port();

	virtual Connection* CreateConnection(const SocketAddress& remote_addr) = 0;

	Connection* GetConnection(const SocketAddress& remote_addr);

	// Called when a packet is received from an unknown address that is not
	// currently a connection. 
	// Create passive connections now
	// void OnReadPacket(const char* data, size_t size, const SocketAddress& addr);

	// Sends the given packet to the given address, provided that the address is
	// that of a connection or an address that has sent to us already.
	virtual int SendTo(const void* data, size_t size, const SocketAddress& addr) = 0;

	int GetError()	{ return error_; }

	//////////////////////////////////////////////////////////////////////////
	void AddConnListener(IConnListener * listener) { listener_set_.insert(listener); }
	void RemoveConnListener(IConnListener * listener) { listener_set_.erase(listener); }

protected:
	evutil_socket_t socket_;

	// Adds the given connection to the list.  (Deleting removes them.)
	void AddConnection(Connection* conn);
	void RemoveConnection(Connection* conn);

	virtual void OnCreated(IConnection* pConn);
	virtual void OnConnected(IConnection* pConn);
	virtual void OnReadPacket(IConnection* pConn, const char* data, size_t size);
	virtual void OnWritable(IConnection* pConn, bool writable);
	virtual void OnClose(IConnection* pConn, int err);
	virtual void OnError(IConnection* pConn, int err);
	virtual void OnDestroy(IConnection* pConn); 

	typedef std::map<SocketAddress, Connection*> AddressMap;
	AddressMap connections_;
	int32 error_;

private:

	std::set<IConnListener *> listener_set_;

	friend class Connection;
	friend class ProxyConnection;
	friend class TCPConnection;
};

//Connection will send probing packets periodically, to get the OWD of both directions.
//Do we need three-way handshake probing packets, so that both ends can have the OWDs info with minimal overhead?
//We only probe the upstream now.

struct NtdMetrics{
	NtdMetrics()
	{
		conv_id = 0;
		proto_type = IConnection::UNKNOWN;
		owd = 0;
		seq = 0;
		reverse_owd = 0;
		loss = 0;
		rtt = 0;
		recv_rate = 0;
		send_rate = 0;
		tick = 0;
	}

	std::string Desc()
	{
		std::ostringstream os;
		os  << "["<< proto_type << "]"
			<< " conv_id = "<< conv_id 
			<< " seq = "<< seq
			<< " tick = " << tick
			<< " rtt = " << rtt
			<< " owd = " << owd
			<< " reverse_owd = " << reverse_owd
			<< " loss = " << loss
			<< " send_rate = " << send_rate
			<< " recv_rate = " << recv_rate;
		return os.str().c_str();
	}

	IConnection::PROTO_TYPE proto_type;
	uint16 conv_id;
	int32 seq;
	uint32 owd;
	uint32 reverse_owd;
	int32 loss;
	uint32 rtt;
	int32 recv_rate;
	int32 send_rate;
	int32 tick;
};

class IConnMonitor
{
public:
	virtual void AddSample(const NtdMetrics* pMetrics) = 0;
};

class Connection : public IConnection, public INTDTimerListener, public NtdProtoParser
{
public:

	// Constructs a new connection to the given remote port.
	Connection(Port* port, const SocketAddress& remote_addr);

	virtual ~Connection();

	virtual uint16 GetConvId() { return conv_id_; }

	void AttachConnMonitor(IConnMonitor* monitor) { monitor_set_.insert(monitor); }
	void DetachConnMonitor(IConnMonitor* monitor) { monitor_set_.erase(monitor); }

	//Set interval of probing, if set to INT_MAX, will disable active probing, but still can process probing packets
	//Default 10 ms
	virtual void SetProbingInterval(int ms);

	//if disabled, the connection will not process probing packets and will forward all data read to the app layer
	virtual void EnablePing(bool enabled);

	virtual PROTO_TYPE GetProtoType() { return proto_type_; }
	void SetProtoType(PROTO_TYPE proto_type) { proto_type_ = proto_type; }

	//Port * port();

	// Determines whether the connection has finished connecting.  This can only
	// be false for TCP connections.
	virtual bool Connected() const { return connected_; }
    void SetConnected(bool value);

	//send data out, the underlying connection maybe TCP or UDP, but we treat both like packet connection,
	//which means, if succ, all data can be thought as sent, if failed, no data sent.
	//as for tcp, the half sent data will buffered internally and auto sent out when writable 
	//the data will add header when necessary for probing, the app layer has no need to worry about this.
	virtual int Send(byte* data, size_t size);

	virtual void OnReadPacket(const char* data, size_t size);

	virtual const SocketAddress& RemoteAddress() { return remote_addr_; }

	virtual int GetError() { return error_; }

	virtual void OnTimer(NTDTimer* pTimer);

	//////////////////////////////////////////////////////////////////////////
	virtual void HandleProtoData(NtdProtoData * proto, void* pContext);
	virtual void HandleProtoRawData(NtdProtoRawData * proto, void* pContext);
	virtual void HandleProtoFeedback(NtdProtoFeedback * proto, void* pContext);
	
	virtual bool IsTimetoPing(uint32 now);
	virtual void DoPing();

	bool Flush();

protected:
	void AddSample(const NtdMetrics& metrics);

	int32 SendOutputbuf();

	//we don't guarantee send the whole data out, it's the caller's responsibility to do this.
	virtual int DoSend(const void* data, size_t size) = 0;

	bool HasRemainPDU();
	bool TrySend(NtdProto* proto, bool urgent = false);


protected:
	int32 error_;

	Port* port_;
	SocketAddress remote_addr_;

	bool connected_;

	size_t recv_total_bytes_;
    size_t sent_total_bytes_;

	NTDTimer probe_timer_;
	int interval_;
	uint32 last_ping_sent_;//we can use APP data as ping

	//we only pack one PDU at a time in the buffer for we don't want to mix up the PDUs
	//so another queue for storing the PDU is needed, if not for underlying ping and acks, 
	//there's no need to do the cache here
	//TODO: seems abstract another specific connection to handle this sounds more reasonable
	evbuffer * evbuf_output_;
	evbuffer * evbuf_input_;

	std::set<IConnMonitor *> monitor_set_;
	PROTO_TYPE proto_type_;

	int32 owd_base_;
	uint32 last_io_ts_;
	uint32 start_ts_;
	uint32 life_time_;
	int32 seq_expt_;
	int32 seq_init_;
	uint32 pkt_loss_;

	int32 ptk_seq_;
	uint16 conv_id_;

	int32 smoothed_rtt_;

	std::deque<NtdProto*> pdu_queue_;

	bool ping_enabled_;
};

// ProxyConnection defers all the interesting work to the port

class ProxyConnection : public Connection {
public:
	ProxyConnection(Port* port, const SocketAddress& remote_addr);
	virtual ~ProxyConnection();

	virtual int DoSend(const void* data, size_t size);
};

//////////////////////////////////////////////////////////////////////////
class IOEventHandle
{
public:
	virtual void OnRead() {}
	virtual void OnWrite() {}
};

//////////////////////////////////////////////////////////////////////////
//                       UDPPort
//////////////////////////////////////////////////////////////////////////
class UDPPort : public Port, public IOEventHandle {
public:
	UDPPort(const SocketAddress& address);
	virtual ~UDPPort();

	virtual Connection* CreateConnection(const SocketAddress& remote_addr);

	//virtual int GetError();

	virtual void OnRead();
	virtual void OnWrite();

protected:

	// Handles sending using the local UDP socket.
	virtual int SendTo(const void* data, size_t size, const SocketAddress& addr);

	// Dispatches the given packet to the port or connection as appropriate.
	void OnReadPacket(const char* data, size_t size, const SocketAddress& remote_addr);

	void NotifyWriatable(bool writable);

private:
	event * read_ev_;
	event * write_ev_;

};


//////////////////////////////////////////////////////////////////////////
//TCPPort
//////////////////////////////////////////////////////////////////////////


class TCPConnection;

class TCPPort : public Port, public IOEventHandle {
public:
	TCPPort(const SocketAddress& address);
	virtual ~TCPPort();

	virtual Connection* CreateConnection(const SocketAddress& remote_addr);

	virtual int GetError();

	virtual void OnRead();

protected:
	// Handles sending using the local TCP socket.
	virtual int SendTo(const void* data, size_t size, const SocketAddress& addr);

	// Creates TCPConnection for incoming sockets
	void OnAcceptEvent();

private:
	SocketAddress address_;
	bool incoming_only_;

	struct Incoming {
		SocketAddress addr;
		evutil_socket_t socket;
	};
	std::list<Incoming> incoming_;

	event * listen_ev_;

	evutil_socket_t GetIncoming(const SocketAddress& addr, bool remove = false);

	// Receives packet signal from the local TCP Socket.
	// void OnReadPacket(const char* data, size_t size, const SocketAddress& remote_addr);

	friend class TCPConnection;
};


class TCPConnection : public Connection, public IOEventHandle
{
public:
	// Connection is outgoing unless socket is specified
	TCPConnection(TCPPort* port, const SocketAddress& remote_addr, evutil_socket_t socket=INVALID_SOCKET);
	virtual ~TCPConnection();

	virtual int DoSend(const void* data, size_t size);

	virtual void OnRead();
	virtual void OnWrite();

	evutil_socket_t socket() { return socket_; }

private:
	evutil_socket_t socket_;
	event * read_ev_;
	event * write_ev_;
    
	void OnConnect();
	void OnClose();
	//virtual void OnReadPacket(const char* data, size_t size);

	friend class TCPPort;
};

#endif//__PORT_H__